﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Net;
using ToyundaPhone.FileFormat;
using ToyundaPhone.Database;
using System.Collections.Generic;

namespace ToyundaPhone.Helpers
{
	public class SongListUpdater
	{
		public event EventHandler UpdateCompleted;
		public event EventHandler<UpdateStateEventArgs> UpdateFailed;
		public event EventHandler<UpdateProgressEventArgs> UpdateProgressChanged;
		public event EventHandler<UpdateStateEventArgs> UpdateStateChanged;
		public event EventHandler UpdateStarted;

		private Uri songListUri;

		public SongDatabase Database { get; set; }
		private bool isRunning;

		public bool IsRunning
		{
			get { lock(this) return isRunning; }
			private set { lock (this) isRunning = value; }
		}

		private UpdateState state;

		public UpdateState State
		{
			get { lock(this) return state; }
			private set { lock(this) state = value; }
		}

		public SongListUpdater(Uri sourceUri)
		{
			this.songListUri = sourceUri;
		}

		public void Start()
		{
			if (Database == null) return;
			if (IsRunning) return;
			IsRunning = true;
			BackgroundWorker bw = new BackgroundWorker();
			bw.DoWork += StartUpdate;
			bw.RunWorkerAsync();
		}

		private void StartUpdate(object sender, DoWorkEventArgs e)
		{
			OnUpdateStarted();
			try
			{
				DoServerRequest();
				State = UpdateState.WebRequest;
				OnUpdateStateChanged(State);
			}
			catch (Exception ex)
			{
				OnUpdateFailed(UpdateState.Start, ex);
				CloseUpdate();
			}
		}

		private void WebClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
		{
			WebClient client = e.UserState as WebClient;
			State = UpdateState.WebRequest;
			OnUpdateStateChanged(State);
			try
			{
				SongDatabase db = Database;
				List<Song> unusedSongs = new List<Song>(from s in db.Songs select s);
				System.IO.Stream source = e.Result;
				DoParseStream(db, unusedSongs, source);
				DoCommit(db);
				DoDeleteUnused(db, unusedSongs);
				OnUpdateCompleted();
			}
			catch (Exception ex)
			{
				OnUpdateFailed(State, ex);
			}
			finally
			{
				CloseUpdate();
			}
		}

		private void DoServerRequest()
		{
			State = UpdateState.Start;
			OnUpdateStateChanged(State);
			WebClient client = new WebClient();
			client.AllowReadStreamBuffering = true;
			client.Encoding = System.Text.Encoding.UTF8;
			client.OpenReadCompleted += WebClient_OpenReadCompleted;
			client.OpenReadAsync(songListUri, client);
		}

		private void DoParseStream(SongDatabase db, List<Song> unusedSongs, System.IO.Stream source)
		{
			string[] srow = new string[3];
			using (System.IO.TextReader reader = new System.IO.StreamReader(source))
			{
				State = UpdateState.Parse;
				OnUpdateStateChanged(State);
				GenericDelimitedFileLexer lexer = new DashDelimitedFileLexer(reader);
				foreach (var row in new NameCleaningRowFilter(new FixedTextColumnCountFilter(new DelimitedFileParser(lexer), 3, new string(lexer.Delimiter))).AsEnumerable())
				{
					OnUpdateProgressChanged(source.Position, source.Length);
					for (int i = 0; i < row.Length; i++)
					{
						if (row[i] == null)
						{
							srow[i] = string.Empty;
						}
						else if (!(row[i] is string))
						{
							srow[i] = row[i].ToString();
						}
						else
						{
							srow[i] = (string)row[i];
						}
					}
					if ((srow[0].Length > 15) || (srow[1].Length > 100) || (srow[2].Length > 200))
					{
						continue; // TODO: Really handle the error.
					}
					var song = (from s in db.Songs where s.Type == srow[0] && s.Work == srow[1] && s.Title == srow[2] select s).FirstOrDefault();
					if (song == null)
					{
						song = new Song() { Type = srow[0], Work = srow[1], Title = srow[2] };
						db.Songs.InsertOnSubmit(song);
					}
					unusedSongs.Remove(song);
				}
			}
		}

		private void DoCommit(SongDatabase db)
		{
			State = UpdateState.Commit;
			OnUpdateStateChanged(State);
			db.SubmitChanges();
		}

		private void DoDeleteUnused(SongDatabase db, List<Song> unusedSongs)
		{
			State = UpdateState.Delete;
			OnUpdateStateChanged(State);
			if (unusedSongs.Count() > 0)
			{
				db.Songs.DeleteAllOnSubmit(unusedSongs);
				db.SubmitChanges();
			}
		}

		private void CloseUpdate()
		{
			IsRunning = false;
		}

		protected virtual void OnUpdateStarted()
		{
			if (UpdateStarted != null)
			{
				UpdateStarted(this, new EventArgs());
			}
		}

		protected virtual void OnUpdateProgressChanged(long read, long length)
		{
			if (UpdateProgressChanged != null)
			{
				UpdateProgressChanged(this, new UpdateProgressEventArgs(read, length));
			}
		}

		protected virtual void OnUpdateCompleted()
		{
			if (UpdateCompleted != null)
			{
				UpdateCompleted(this, new EventArgs());
			}
		}

		protected virtual void OnUpdateFailed(UpdateState state, Exception e)
		{
			if (System.Diagnostics.Debugger.IsAttached)
			{
				System.Diagnostics.Debugger.Break();
			}
			if (UpdateFailed != null)
			{
				UpdateFailed(this, new UpdateStateEventArgs(state));
			}
		}

		protected virtual void OnUpdateStateChanged(UpdateState state)
		{
			if (UpdateStateChanged != null)
			{
				UpdateStateChanged(this, new UpdateStateEventArgs(state));
			}
		}
	}
}
